﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Serialization;
using BuilderLibrary;
using Deep_Talk_Converter.Properties;
using DeepZoomPivotConstructor.DTBuilderLib;
using DeepZoomPivotConstructor.ErrorAndLoggingInterfaces;
using DeepZoomPivotConstructor.VisualCacheXMLDirectory;
using DeepZoomPivotConstructor.VisualLib;
using DeepZoomPivotConstructor.VisualLib.Hotspots;
using RenderEngineInterfaces;

namespace Deep_Talk_Converter.RenderWorkItems
{
    /// <summary>
    /// Do the actual work of rendering a meeting locally. We don't care about the thread we are on...
    /// </summary>
    class RenderMeetingLocally
    {
        private string _url;

        public RenderMeetingLocally(string url)
        {
            _url = url;

            DPIText = 150;
            XBoarderSize = 0.2;
            YBoarderSize = 0.2;

            TextHeight = 0.5;
            FileBackgroundColor = Color.Gray;

            MetaDataFilename = "RenderMetaData.xml";
            RenderDBDirectoryName = "SubImageRenderDB";
        }

        private string RenderDBDirectoryName { get; set; }

        public DirectoryInfo ProjectDir { get; private set; }

        public string Title { get; private set; }

        public delegate void MeetingNameCallback(string meetingName);

        public delegate void ProgressMessageCallback(string message);

        public delegate void RenderDoneCallback(bool ok, string message, string uri);

        /// <summary>
        /// Called when the meeting name is known.
        /// </summary>
        public event MeetingNameCallback MeetingNameKnown;

        /// <summary>
        /// Called when we are notifying people of progress.
        /// </summary>
        public event ProgressMessageCallback ProgressMessage;

        public event RenderDoneCallback RenderDone;

        /// <summary>
        /// Returns a temp directory. Uses default system temp directory unless something else has been specified.
        /// </summary>
        /// <param name="taskName"></param>
        /// <returns></returns>
        private DirectoryInfo GetRenderingTempDirectory()
        {
            string b;
            if (Settings.Default.TempPath == "")
            {
                b = Path.GetTempPath();
            }
            else
            {
                b = Settings.Default.TempPath;
            }

            var d = new DirectoryInfo(b + Path.DirectorySeparatorChar + Path.GetRandomFileName());
            d.Create();
            return d;
        }

        /// <summary>
        /// The render engine doing all the work!
        /// </summary>
        [Import]
        public IRenderEngine MyRenderer { get; set; }

        /// <summary>
        /// The default error logger!
        /// </summary>
        [Import]
        public IErrorLogger MyErrorLogger { get; set; }

        private class RenderSettings
        {
            [Export("RenderingDPI", typeof(int))]
            public int DPI = 150;

            /// <summary>
            /// The agent string we use in URI requests!
            /// </summary>
            [Export("HttpAgentString")]
            public string HttpAgentString = "DeepTalk Standalone GUI Render";

            [Export("RenderingTempDir")]
            public DirectoryInfo RenderingTempDir;

            [Export("RelativePathBase")]
            public DirectoryInfo RelativePathBase;
        }

        /// <summary>
        /// Start the talk render -- download it first to our local disk.
        /// </summary>
        public void Render()
        {
            DirectoryInfo baseBuildDir = null;

            //
            // Settings we will be passing down
            // 

            RenderSettings rs = new RenderSettings();
            rs.DPI = Settings.Default.DPI;
            rs.RenderingTempDir = GetRenderingTempDirectory();
            rs.RelativePathBase = null;

            string baseBuild = null;

            VisualCacheDirectoryDB cache = new VisualCacheDirectoryDB(new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Deep Talk Converter\\Rendered Cache DB"));

            //
            // if we are rendering a file, then we have a "base" directory.
            //

            try
            {
                var f = new FileInfo(_url);
                rs.RelativePathBase = f.Directory;
            }
            catch { }

            //
            // This is a very disconnected app - lots of components that are loaded by MEF. The first part
            // of the app involves setting all of that up. If we fail in this first part then things are
            // very evil... and we have no way, really, of recording an error (but we do our best, eh!?).
            //

            try
            {

                var catalog = new AggregateCatalog();
                CompositionContainer container = new CompositionContainer(catalog);
                CompositionBatch batch = new CompositionBatch();

                batch.AddPart(new RenderEngine.LayoutHelper());
                batch.AddPart(this);
                batch.AddPart(new RenderEngine.RenderControl(cache));

                ///
                /// First job - see if we can find this guy having been rendered before. If so, then
                /// we pick up the cache database left over from last time.
                /// 

                DirectoryInfo oldRenderingDir = FindRenderedDirectory();
                if (oldRenderingDir != null)
                {
                    cache.DBLocation = new DirectoryInfo(oldRenderingDir.FullName + "\\" + RenderDBDirectoryName);
                    baseBuild = oldRenderingDir.FullName;
                }

                ///
                /// Settings we will be passing down
                /// 

                batch.AddPart(rs);

                ///
                /// Next, get all the directories that might have plug-ins.
                /// 

                foreach (var dir in ExternalToolLocations.RenderPluginSearchDirectories.Where(d => d.Exists))
                {
                    catalog.Catalogs.Add(new DirectoryCatalog(dir.FullName));
                }

                ///
                /// Ok -- ready to hook everyone up!
                /// 

                container.Compose(batch);

            }
            catch (Exception e)
            {
                var f = new FileInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Deep Talk Converter\Start Up Failure.txt");
                if (!f.Directory.Exists)
                    f.Directory.Create();
                using (var output = f.CreateText())
                {
                    output.WriteLine(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString());
                    output.WriteLine("Failed to initialize and load components for Deep Talk Converter:");
                    output.WriteLine(e.Message);
                    output.WriteLine(e.StackTrace);
                    output.Close();
                }
                throw;
            }

            //
            // Now that we have the basics of the app setup we can start working on actually rendering the scene.
            //

            try
            {
                if (MyErrorLogger == null)
                    throw new InvalidOperationException("Unable to properly load the components for the application - installation failed!");

                ///
                /// Ready to go!
                /// 

                Visual scene = null;
                Task.Factory.StartNew(() =>
                {
                    scene = MyRenderer.Render(_url);
                }).Wait();

                ///
                /// Did we get anything!?
                /// 

                if (scene == null)
                {
                    SendRenderDone(false, "Failed to render anything! Was what you gave me invalid?", null);
                    return;
                }

                ///
                /// Create a nice title background slide to frame the whole thing
                /// 

                scene = RenderATitleBackground(scene, rs.RenderingTempDir);

                ///
                /// Now that we have a scene we can get the title, and set the location where we will be doing
                /// the rendering. If we alreay know the title, then don't switch over... so if the title changes
                /// the rendered directory will remain the same. Or if the user changes the directory...
                /// 

                string title = "test - output";
                if (scene.Info != null && scene.Info.Title != null && scene.Info.Title.Length > 0)
                {
                    title = scene.Info.Title;
                }
                if (baseBuild == null)
                {
                    baseBuild = Settings.Default.JobOutput + "\\" + NormalizeForDirnames(title);
                    cache.DBLocation = new DirectoryInfo(baseBuild + "\\" + RenderDBDirectoryName);
                }
                baseBuildDir = new DirectoryInfo(baseBuild);
                baseBuildDir.Create();

                ///
                /// Ok - write out the rendering meta data. The earlier we can do this, the sooner we will
                /// be able to recover any DZ creations we do below!
                /// 

                RenderMetaData renderMetaData = new RenderMetaData();
                renderMetaData.Url = _url;
                {
                    XmlSerializer xm = new XmlSerializer(typeof(RenderMetaData));
                    using (var fs = File.Create(baseBuild + @"\" + MetaDataFilename))
                    {
                        xm.Serialize(fs, renderMetaData);
                    }
                }

                ///
                /// Now do the deep zoom construction.
                ///

                SendProgressMessage("Creating DeepZoom image");
                DeepZoomBuilder dz = new DeepZoomBuilder(MyErrorLogger, cache);
                dz.RenderVisual(new DirectoryInfo(baseBuild + "\\meeting"), scene);

                ///
                /// Finally, move over the various files we need to use this thing.
                /// 

                SendProgressMessage("Finalizing Directory Creation");

                FileInfo startPage = WebSiteBuilder.CreateWebSite(baseBuildDir, title + " - DeepTalk");
                var shortcutFile = WebSiteBuilder.CreateLocalStart(baseBuildDir);
                ProjectDir = baseBuildDir;

                ///
                /// Done!
                /// 

                SendRenderDone(true, "Render finished ok!", shortcutFile.FullName);

                ///
                /// Delete now that done. If we had a crash these will be left behind...
                /// 

                rs.RenderingTempDir.Delete(true);
            }
            catch (Exception e)
            {
                MyErrorLogger.LogMessage("GUIException", "Error: " + e.ToString() + " - " + e.StackTrace);
                SendRenderDone(false, e.ToString(), "");
            }

            ///
            /// A special place to be writing log files - we want them moved over to our home log file now...
            /// 

            if (baseBuildDir != null)
            {
                MyErrorLogger.SetLogfileLocation(baseBuildDir);
            }
        }

        /// <summary>
        /// Look through all rendered directories where we are going to write this out
        /// and see if we can find a directory that contains this rendering.
        /// </summary>
        /// <returns></returns>
        private DirectoryInfo FindRenderedDirectory()
        {
            ///
            /// If there are no renderings, then we really can't do much!
            /// 

            if (!Directory.Exists(Settings.Default.JobOutput))
            {
                return null;
            }

            ///
            /// See if we have already rendered this guy.
            /// 

            var dirs = from d in Directory.GetDirectories(Settings.Default.JobOutput)
                       where GetDirectoryURL(new DirectoryInfo(d)) == _url
                       select new DirectoryInfo(d);
            return dirs.FirstOrDefault();
        }

        public string MetaDataFilename { get; set; }

        /// <summary>
        /// For a given directory, load the meta-data and read in the URL and return it.
        /// </summary>
        /// <param name="dir"></param>
        /// <returns></returns>
        private string GetDirectoryURL(DirectoryInfo dir)
        {
            FileInfo cacheFile = new FileInfo(dir.FullName + "\\" + MetaDataFilename);
            if (cacheFile.Exists)
            {
                XmlSerializer ser = new XmlSerializer(typeof(RenderMetaData));
                using (var fs = cacheFile.OpenRead())
                {
                    RenderMetaData md = ser.Deserialize(fs) as RenderMetaData;
                    fs.Close();
                    if (md != null)
                    {
                        return md.Url;
                    }
                }
            }
            return null;
        }

        private int DPIText { get; set; }

        private double XBoarderSize { get; set; }
        private double YBoarderSize { get; set; }

        private double TextHeight { get; set; }

        private Color FileBackgroundColor { get; set; }

        /// <summary>
        /// Add a background slide with a title on it.
        /// </summary>
        /// <param name="scene"></param>
        /// <returns></returns>
        private Visual RenderATitleBackground(Visual visual, DirectoryInfo tempDir)
        {
            ///
            /// Get the slides setup correctly
            /// 

            visual.ScaleToWidth(10.0);

            ///
            /// Build the title page
            /// 

            VisualTitleAndBackground title = new VisualTitleAndBackground(visual)
            {
                {TextHeight, visual.Info.Title, new HSActionLink(new Uri(_url))}
            };
            title.ImageWritingDirectory = tempDir;

            return title;
        }

        /// <summary>
        /// Cleans out a directory.
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        private string NormalizeForDirnames(string dirname)
        {
            List<char> badcharList = new List<char>();
            badcharList.AddRange(Path.GetInvalidPathChars());
            badcharList.Add(':');
            badcharList.Add('\\');
            badcharList.Add('/');
            var badchars = badcharList.ToArray();
            int loc = dirname.IndexOfAny(badchars);
            while (loc >= 0)
            {
                dirname = dirname.Replace(dirname[loc], ' ');
                loc = dirname.IndexOfAny(badchars);
            }
            dirname = dirname.Replace(':', ' ');
            return dirname.Trim();
        }

        private void SendProgressMessage(string p)
        {
            if (ProgressMessage != null)
            {
                ProgressMessage(p);
            }
        }

        private void SendMeetingKnown(string p)
        {
            if (MeetingNameKnown != null)
            {
                MeetingNameKnown(p);
            }
        }

        private void SendRenderDone(bool ok, string message, string uri)
        {
            if (RenderDone != null)
            {
                RenderDone(ok, message, uri);
            }
        }
    }
}
